#include "../global.h"
#include "LMMaster.h"
#include "../LMContext.h"
#include <boost/lexical_cast.hpp>
#include "../xpt/xptClient.h"
#include "../algorithm/algorithm.h"

LMMaster::LMMaster(boost::shared_ptr<LMContext> config)
	: _context(config)
{
	for(int i = 0; i < 32; ++i)
	{
		algorithmInited[i] = 0;
	}
	memset(&workDataSource, 0, sizeof(workDataSource));

	InitializeCriticalSection(&workDataSource.cs_work);
	InitializeCriticalSection(&(_context->cs_xptClient));
}

LMMaster::~LMMaster()
{

}

void LMMaster::login()
{
	boost::asio::io_service io_service;
	boost::asio::ip::tcp::resolver resolver(io_service); //resolve dns
	boost::asio::ip::tcp::resolver::query query(_context->PoolIp(), boost::lexical_cast<std::string>(_context->PoolPort()));
	boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
	boost::asio::ip::tcp::resolver::iterator end;  
	boost::asio::ip::tcp::endpoint endpoint;
	boost::scoped_ptr<boost::asio::ip::tcp::socket> socket;
	boost::system::error_code error_socket = boost::asio::error::host_not_found;
	while (error_socket && endpoint_iterator != end)  
	{  
		endpoint = *endpoint_iterator ; 
		std::cout << "connecting to " << endpoint << std::endl;
		socket.reset(new boost::asio::ip::tcp::socket(io_service));
		socket->connect(endpoint, error_socket);  
		endpoint_iterator++ ;  
	}

	boost::asio::ip::tcp::no_delay nd_option(true);
	boost::asio::socket_base::keep_alive ka_option(true);
	socket->set_option(nd_option);
	socket->set_option(ka_option);

	if (error_socket) {
		std::cout << error_socket << std::endl;
		LMEXCEPTION_THROW(LMException, "Cannot resolve pool location: " + error_socket.message());
	}

	minerSettings.useGPU = false;
	minerSettings.threadCount = _context->ThreadNum();
	minerSettings.requestTarget.ip = _strdup(endpoint.address().to_string().c_str());
	minerSettings.requestTarget.port = _context->PoolPort();
	minerSettings.requestTarget.authUser = _strdup(_context->User().c_str());
	minerSettings.requestTarget.authPass = _strdup(_context->Password().c_str());
}

bool LMMaster::isLoggedin()
{
	return !xptClient_isDisconnected(_context->xptClient, NULL);
}

void LMMaster::run()
{
	_context->xptClient = xptClient_create();
	if( _context->xptClient == NULL )
		LMEXCEPTION_THROW(LMException, "Cannot create xpt client.");

	// set developer fees
	// up to 8 fee entries can be set
	// the fee base is always calculated from 100% of the share value
	// for example if you setup two fee entries with 3% and 2%, the total subtracted share value will be 5%
	//xptClient_addDeveloperFeeEntry(xptClient, "Ptbi961RSBxRqNqWt4khoNDzZQExaVn7zL", getFeeFromDouble(0.5)); // 0.5% fee (jh00, for testing)
	uint32 timerPrintDetails = GetTickCount() + 8000;
	while( true )
	{
		uint32 currentTick = GetTickCount();
		if( currentTick >= timerPrintDetails )
		{
			// print details only when connected
			if( xptClient_isDisconnected(_context->xptClient, NULL) == false )
			{
				boost::posix_time::time_duration passed_duration = boost::posix_time::microsec_clock::universal_time() - _context->minerStartTime();
				double speedRate = 0.0;
				if( workDataSource.algorithm == ALGORITHM_RIECOIN )
				{
					// speed is represented as ch/s
					float speedRate_2ch = 0.0f;
					float speedRate_3ch = 0.0f;
					float speedRate_4ch = 0.0f;
					float speedRate_sps = 0.0f;
					if( passed_duration.total_seconds() > 5 )
					{
						speedRate_2ch = (double)total2ChainCount / (double)passed_duration.total_seconds();
						speedRate_3ch = (double)total3ChainCount / (double)passed_duration.total_seconds();
						speedRate_4ch = (double)total4ChainCount / (double)passed_duration.total_seconds();
						speedRate_sps = (double)totalSieveCount / (double)passed_duration.total_seconds();
					}
					printf("[%02d:%02d:%02d] 2ch/s: %.4lf 3ch/s: %.4lf 4ch/s: %.4lf sps: %.2lf Shares: %d / %d\n", (passed_duration.total_seconds()/3600)%60, (passed_duration.total_seconds()/60)%60, (passed_duration.total_seconds())%60, speedRate_2ch, speedRate_3ch, speedRate_4ch, speedRate_sps, totalShareCount, totalShareCount-totalRejectedShareCount);
				}
				else if(workDataSource.algorithm == 0)
					std::cout << "Haven't got work from master." << std::endl;
				else
					LMEXCEPTION_THROW(LMException, "Unknown algorithm: " + boost::lexical_cast<std::string>(workDataSource.algorithm));

			}
			timerPrintDetails = currentTick + 8000;
		}
		// check stats
		if( xptClient_isDisconnected(_context->xptClient, NULL) == false )
		{
			EnterCriticalSection(&_context->cs_xptClient);
			xptClient_process(_context->xptClient);
			if( _context->xptClient->disconnected )
			{
				// mark work as invalid
				EnterCriticalSection(&workDataSource.cs_work);
				workDataSource.height = 0;
				monitorCurrentBlockHeight = 0;
				LeaveCriticalSection(&workDataSource.cs_work);
				// we lost connection :(
				printf("Connection to server lost - Reconnect in 45 seconds\n");
				xptClient_forceDisconnect(_context->xptClient);
				LeaveCriticalSection(&_context->cs_xptClient);
				// pause 45 seconds
				Sleep(45000);
			}
			else
			{
				// is known algorithm?
				if( _context->xptClient->clientState == XPT_CLIENT_STATE_LOGGED_IN && (_context->xptClient->algorithm != ALGORITHM_PROTOSHARES && _context->xptClient->algorithm != ALGORITHM_SCRYPT && _context->xptClient->algorithm != ALGORITHM_METISCOIN && _context->xptClient->algorithm != ALGORITHM_MAXCOIN && _context->xptClient->algorithm != ALGORITHM_RIECOIN) )
				{
					printf("The login is configured for an unsupported algorithm.\n");
					printf("Make sure you miner login details are correct\n");
					// force disconnect
					//xptClient_free(xptClient);
					//xptClient = NULL;
					xptClient_forceDisconnect(_context->xptClient);
					LeaveCriticalSection(&_context->cs_xptClient);
					// pause 45 seconds
					Sleep(45000);
				}
				else if( _context->xptClient->blockWorkInfo.height != workDataSource.height || memcmp(_context->xptClient->blockWorkInfo.merkleRoot, workDataSource.merkleRootOriginal, 32) != 0  )
				{
					// update work
					xptMiner_getWorkFromXPTConnection();
					LeaveCriticalSection(&_context->cs_xptClient);
				}
				else
					LeaveCriticalSection(&_context->cs_xptClient);
				// update time monitor
				if( workDataSource.height > 0 )
					monitorCurrentBlockTime = (uint32)time(NULL) + workDataSource.timeBias;
				Sleep(1);
			}
		}
		else
		{
			// initiate new connection
			EnterCriticalSection(&_context->cs_xptClient);
			if( xptClient_connect(_context->xptClient, &minerSettings.requestTarget) == false )
			{
				LeaveCriticalSection(&_context->cs_xptClient);
				printf("Connection attempt failed, retry in 45 seconds\n");
				Sleep(45000);
			}
			else
			{
				LeaveCriticalSection(&_context->cs_xptClient);
				printf("Connected to server using x.pushthrough(xpt) protocol\n");
				totalCollisionCount = 0;
				total2ChainCount = 0;
				total3ChainCount = 0;
				total4ChainCount = 0;
			}
			Sleep(1);
		}
	}
}

void LMMaster::xptMiner_getWorkFromXPTConnection()
{
	EnterCriticalSection(&workDataSource.cs_work);
	if( _context->xptClient->algorithm >= 0 && _context->xptClient->algorithm < 32 && _context->xptClient->blockWorkInfo.height > 0 )
	{
		if(_context->xptClient->algorithm != ALGORITHM_RIECOIN)
			LMEXCEPTION_THROW(LMException, "Unknown algorithm: " + boost::lexical_cast<std::string>(workDataSource.algorithm));

		if( _context->xptClient->algorithm == ALGORITHM_RIECOIN && algorithmInited[_context->xptClient->algorithm] == 0 )
		{
			riecoin_init();
			algorithmInited[_context->xptClient->algorithm] = 1;
		}	
	}
	workDataSource.algorithm = _context->xptClient->algorithm;
	workDataSource.version = _context->xptClient->blockWorkInfo.version;
	workDataSource.timeBias = _context->xptClient->blockWorkInfo.timeBias;
	workDataSource.nBits = _context->xptClient->blockWorkInfo.nBits;
	memcpy(workDataSource.merkleRootOriginal, _context->xptClient->blockWorkInfo.merkleRoot, 32);
	memcpy(workDataSource.prevBlockHash, _context->xptClient->blockWorkInfo.prevBlockHash, 32);
	memcpy(workDataSource.target, _context->xptClient->blockWorkInfo.target, 32);
	memcpy(workDataSource.targetShare,_context-> xptClient->blockWorkInfo.targetShare, 32);
	workDataSource.targetCompact = _context->xptClient->blockWorkInfo.targetCompact;
	workDataSource.shareTargetCompact = _context->xptClient->blockWorkInfo.targetShareCompact;
	workDataSource.coinBase1Size = _context->xptClient->blockWorkInfo.coinBase1Size;
	workDataSource.coinBase2Size = _context->xptClient->blockWorkInfo.coinBase2Size;
	memcpy(workDataSource.coinBase1, _context->xptClient->blockWorkInfo.coinBase1, _context->xptClient->blockWorkInfo.coinBase1Size);
	memcpy(workDataSource.coinBase2, _context->xptClient->blockWorkInfo.coinBase2, _context->xptClient->blockWorkInfo.coinBase2Size);

	// get hashes
	if( _context->xptClient->blockWorkInfo.txHashCount > MAX_TRANSACTIONS )
	{
		printf("Too many transaction hashes\n"); 
		workDataSource.txHashCount = 0;
	}
	else
		workDataSource.txHashCount = _context->xptClient->blockWorkInfo.txHashCount;
	for(uint32 i=0; i<_context->xptClient->blockWorkInfo.txHashCount; i++)
		memcpy(workDataSource.txHash+32*(i+1), _context->xptClient->blockWorkInfo.txHashes+32*i, 32);
	// set blockheight last since it triggers reload of work
	if( workDataSource.height == 0 && _context->xptClient->blockWorkInfo.height != 0 )
	{
		_context->resetMinerStartTime();
		printf("[00:00:00] Start mining\n");
	}
	workDataSource.height = _context->xptClient->blockWorkInfo.height;
	LeaveCriticalSection(&workDataSource.cs_work);
	monitorCurrentBlockHeight = workDataSource.height;
}
